#include "http_request.h"
#include "logger/logger.h"
#include <algorithm>
#include <regex>

const char CRLF[] = "\r\n";

namespace zh
{
    const std::unordered_set<std::string> HttpRequest::s_DefaultHTML {"/index",
                                                                      "/welcome",
                                                                      "/video",
                                                                      "/picture",
                                                                      "/login",
                                                                      "/register"};

    void HttpRequest::initialize()
    {
        m_parseState = ParseState::REQUEST_LINE;
        m_method.clear();
        m_version.clear();
        m_path.clear();
        m_body.clear();
        m_header.clear();
        m_post.clear();
    }

    bool HttpRequest::parse(Buffer& buffer)
    {
        if (buffer.readableBytes() <= 0)
        {
            return false;
        }

        while (buffer.readableBytes() > 0 && m_parseState != ParseState::FINISHED)
        {
            const char* lineEndPtr = std::search(buffer.curReadPtr(), buffer.curWritePtr(), CRLF, CRLF + 2);
            std::string line(buffer.curReadPtr(), lineEndPtr);

            // LOG_INFO("current parse content: {}", line);
            switch (m_parseState)
            {
                case ParseState::REQUEST_LINE:
                    if (!parseLine(line))
                    {
                        // LOG_INFO("===!parseLine(line)");
                        return false;
                    }
                    parsePath();
                    break;
                case ParseState::REQUEST_HEADER:
                    parseHeader(line);
                    if (buffer.readableBytes() <= 2)
                    {
                        m_parseState = ParseState::FINISHED;
                    }
                    break;
                case ParseState::REQUEST_BODY:
                    parseBody(line);
                    break;

                default:
                    break;
            }
            if (lineEndPtr == buffer.curWritePtr())
            {
                break;
            }
            buffer.updateReadPtr(lineEndPtr + 2 - buffer.curReadPtr());
        }
        LOG_INFO("m_method: {}, m_path: {}, m_version: {}, m_body: {}", m_method, m_path, m_version, m_body);
        return true;
    }

    bool HttpRequest::isKeepAlive() const
    {
        auto it = m_header.find("Connection");
        if (it != m_header.end())
        {
            // LOG_INFO("===isKeepAlive");
            return it->second == "keep-alive" && m_version == "1.1";
        }
        return false;
    }

    std::string HttpRequest::getPath() const
    {
        return m_path;
    }

    std::string HttpRequest::getMethod() const
    {
        return m_method;
    }

    bool HttpRequest::parseLine(const std::string& line)
    {
        std::regex  pattern("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
        std::smatch subMatch;
        if (std::regex_match(line, subMatch, pattern))
        {
            m_method  = subMatch[1];
            m_path    = subMatch[2];
            m_version = subMatch[3];

            m_parseState = ParseState::REQUEST_HEADER;
            return true;
        }
        return false;
    }

    bool HttpRequest::parseHeader(const std::string& line)
    {
        std::regex  pattern("^([^:]*): ?(.*)$");
        std::smatch subMatch;
        if (std::regex_match(line, subMatch, pattern))
        {
            m_header[subMatch[1]] = subMatch[2];
        }
        else
        {
            m_parseState = ParseState::REQUEST_BODY;
            return true;
        }
        return false;
    }

    bool HttpRequest::parseBody(const std::string& line)
    {
        m_body = line;
        parsePost();
        m_parseState = ParseState::FINISHED;
        return true;
    }

    static int convertHex(char character)
    {
        if (character >= 'A' && character <= 'F')
            return character - 'A' + 10;
        if (character >= 'a' && character <= 'f')
            return character - 'a' + 10;
        return character;
    }

    bool HttpRequest::parsePost()
    {
        if (m_method == "POST" && m_header["Content-Type"] == "application/x-www-form-urlencoded")
        {
            if (m_body.empty())
                return false;
            std::string key;
            std::string value;

            int number = 0;
            int length = m_body.length();

            int i = 0;
            int j = 0;
            for (; i < length; i++)
            {
                char character = m_body[i];
                switch (character)
                {
                    case '=':
                        key = m_body.substr(j, i - j);
                        j   = i + 1;
                        break;
                    case '+':
                        m_body[i] = ' ';
                        break;
                    case '%':
                        number = convertHex(m_body[i + 1]) * 16 + convertHex(m_body[i + 2]); // number base on hex
                        // convert hex number to value base on decimalism
                        m_body[i + 2] = number % 10 + '0';
                        m_body[i + 1] = number / 10 + '0';
                        i += 2;
                        break;
                    case '&':
                        value       = m_body.substr(j, i - j);
                        j           = i + 1;
                        m_post[key] = value;
                        break;
                    default:
                        break;
                }
            }
            assert(j <= i);
            if (m_post.count(key) == 0 && j < i)
            {
                value       = m_body.substr(j, i - j);
                m_post[key] = value;
            }
            // it's just a test for displaying the welcome page
            if (m_post.count("username") && m_post.count("password"))
            {
                m_path = "/welcome";
                parsePath();
            }
        }
        // TODO: Content-Type: application/json
        return true;
    }

    bool HttpRequest::parsePath()
    {
        if (m_path == "/")
        {
            m_path = "/index.html";
            return true;
        }

        if (s_DefaultHTML.count(m_path))
        {
            m_path += ".html";
            return true;
        }

        return false;
    }

} // namespace zh